package io.gitee.caoxiaoyu97.log4j.defender.message.trace;

import io.gitee.caoxiaoyu97.log4j.defender.matcher.KeyMatcher;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class StringMessageDefender extends MessageDefender {
    private static final Log logger = LogFactory.getLog(StringMessageDefender.class);
    private Set<StringReplacer> replacers = new HashSet<>();


    public StringMessageDefender(List<KeyMatcher> matchers, String maxLen, String markerValue) {
        super(matchers, maxLen, markerValue);
        for (KeyMatcher matcher : this.matchers) {
            this.replacers.addAll((Collection<? extends StringReplacer>) matcher.getOriginKeys().stream()
                    .map(it -> new StringReplacer(it, this.maxLen, this.markerValue)).collect(Collectors.toList()));
        }
    }


    public String doDesensitize(String input) {
        return deSensitiveString(new StringBuilder(input)).toString();
    }

    protected StringBuilder deSensitiveString(StringBuilder inputOrigin) {
        StringBuilder input = inputOrigin;
        for (StringReplacer replacer : this.replacers) {
            input = replacer.isQuickMatch(input) ? replacer.replace(input) : input;
        }
        return input;
    }


    protected class Marker {
        private String start;
        private String objEnd;
        private String valueEnd;
        private String quote;

        public Marker(String key, String quote) {
            this.start = quote + key + quote + ":";
            this.objEnd = quote + "}";
            this.valueEnd = quote + ",";
            this.quote = quote;
        }

        public String getStart() {
            return this.start;
        }

        public void setStart(String start) {
            this.start = start;
        }

        public String getObjEnd() {
            return this.objEnd;
        }

        public void setObjEnd(String objEnd) {
            this.objEnd = objEnd;
        }

        public String getValueEnd() {
            return this.valueEnd;
        }

        public void setValueEnd(String valueEnd) {
            this.valueEnd = valueEnd;
        }

        public String getQuote() {
            return this.quote;
        }

        public void setQuote(String quote) {
            this.quote = quote;
        }
    }

    public class StringReplacer {
        private Set<StringMessageDefender.Marker> markers = new HashSet<>();
        private String key;
        private Integer maxLen;
        private String markerValue;

        public StringReplacer(String key, Integer maxLen, String markerValue) {
            this.markers.add(new StringMessageDefender.Marker(key, "\""));
            this.markers.add(new StringMessageDefender.Marker(key, "\\\""));
            this.key = key;
            this.maxLen = maxLen;
            this.markerValue = markerValue;
        }

        protected boolean isValidValue(String input) {
            return (input.length() <= this.maxLen.intValue() && !StringMessageDefender.this.invalidValuePattern.matcher(input).matches());
        }

        protected int getValueBegin(StringBuilder input, int keyStart, StringMessageDefender.Marker marker) {
            int quoteIdx = input.indexOf(marker.getQuote(), keyStart + marker.getStart().length());
            return (quoteIdx == -1) ? -1 : (quoteIdx + marker.getQuote().length());
        }

        protected int getQuoteKeyBegin(StringBuilder input, int lastQuoteKeyBegin, StringMessageDefender.Marker marker) {
            return input.indexOf(marker.getStart(), lastQuoteKeyBegin + marker.getStart().length());
        }

        protected int getValueEndByObjEnd(StringBuilder input, int valueBegin, StringMessageDefender.Marker marker) {
            return input.indexOf(marker.getObjEnd(), valueBegin);
        }

        protected int getValueEndByFieldEnd(StringBuilder input, int valueBegin, StringMessageDefender.Marker marker) {
            return input.indexOf(marker.getValueEnd(), valueBegin);
        }

        protected StringBuilder doReplace(StringBuilder inputOrigin, StringMessageDefender.Marker marker) {
            StringBuilder input = inputOrigin;
            try {
                int keyStart = input.indexOf(marker.getStart());
                int lastKeyStart = -1;
                while (keyStart > -1 && keyStart > lastKeyStart) {

                    int valueBegin = getValueBegin(input, keyStart, marker);
                    if (valueBegin == -1) {
                        lastKeyStart = keyStart;

                        keyStart = getQuoteKeyBegin(input, lastKeyStart, marker);

                        continue;
                    }
                    int valueEnd = getValueEndByObjEnd(input, valueBegin, marker);
                    boolean replace = false;
                    if (valueEnd > valueBegin) {
                        String toReplace = input.substring(valueBegin, valueEnd);
                        if (isValidValue(toReplace)) {
                            input = replaceValue(input, valueEnd, valueBegin, this.markerValue);
                            replace = true;
                        }
                    }

                    if (!replace) {

                        valueEnd = getValueEndByFieldEnd(input, valueBegin, marker);
                        if (valueEnd > valueBegin) {
                            String toReplace = input.substring(valueBegin, valueEnd);
                            if (isValidValue(toReplace)) {
                                input = replaceValue(input, valueEnd, valueBegin, this.markerValue);
                            }
                        }
                    }

                    lastKeyStart = keyStart;
                    keyStart = getQuoteKeyBegin(input, lastKeyStart, marker);
                }
                return input;
            } catch (Exception e) {
                StringMessageDefender.logger.debug(e.getMessage());

                return input;
            }
        }

        protected StringBuilder replaceValue(StringBuilder input, int valueEndIdx, int valueBeginIdx, String markerValue) {
            return (new StringBuilder(input.substring(0, valueBeginIdx)))
                    .append(markerValue)
                    .append(input.substring(valueEndIdx));
        }

        public boolean isQuickMatch(StringBuilder input) {
            return (input.indexOf(this.key) > -1);
        }

        public StringBuilder replace(StringBuilder inputOrigin) {
            StringBuilder input = inputOrigin;
            for (StringMessageDefender.Marker marker : this.markers) {
                input = (input.indexOf(marker.getQuote()) > -1) ? doReplace(input, marker) : input;
            }
            return input;
        }
    }
}
